require 'semantictext/extraction_failed'
require 'semantictext/heading'
require 'semantictext/keyword_extractor'
require 'semantictext/date_extractor'
require 'semantictext/extraction_failed'
require 'semantictext/not_header_line'
require 'semantictext/paragraph'
require 'semantictext/span'
require 'semantictext/tag'
require 'string'
require 'semantictext/bulletedlist'
require 'semantictext/bullet'
require 'semantictext/bulleted_list_parser'
require 'semantictext/rich_text_parser'
require 'semantictext/verbatim'
require 'semantictext/default_tag_factory'

module SemanticText
 
  #* I am the root note of a graph of objects that form the document.
  #* I hold metadata. 
  #* I initiate and co-ordinate document-wide operations.
  class Document
    # title of the document
    attr_reader :title

    # date the document was created
    attr_reader :createdAt

    # keyword list for the current document
    attr_reader :keywords 

    # pathname of the file currently being parsed (if it exists, nil otherwise)
    attr_reader :pathname

    # the object model of the parsed document
    attr_reader :content

  def initialize(tag_factory=DefaultTagFactory.new)
    @pathname=nil
    @headers_completed = false
    @content = []
    @current_paragraph = nil
    @verbatim = nil
    @bulleted_list_parser = nil
    @rich_text_parser = RichTextParser.new(tag_factory)
  end

  # export as html
  def export_html
    out = ""
    content.each {|element| out=out+element.export_html}
    out = out + "\n"
  end

  # true iff I have seen the end of the headers section at the top of the document
  def parameters_complete?
    @headers_completed
  end
  
  # parse a document into this object from pathname specified by file
  def parse_from(file)
    @pathname=file
    f = File.new(file)
    f.each_line do |line|
      parse(line)
    end
    f.close
  end

  # parse an individual <i>line</i> of String appending content
  # into the current document held by this object
  def parse(line)
    line.chomp!
    begin
      if (!@headers_completed)
        process_header_line(line) 
      else
        parse_line(line)
      end
      rescue  NotHeaderLine
        @headers_completed = true
        parse_line(line)
    end	
  end
	
    private
	
  def process_header_line(headerLine)
    splitLine = headerLine.split(':',2)
    (attributeName, value) = splitLine
    raise NotHeaderLine.new() if splitLine.size <2
    attributeName.strip!
    @title = value if attributeName=='title'
    @createdAt = DateExtractor.new.extract_from(value) if attributeName=='createdAt'
    @keywords = KeywordExtractor.new.extract_from(value) if attributeName=='keywords'
  end

  def parse_paragraph_line(line)
    if @current_paragraph.nil?
      @current_paragraph = Paragraph.new
      @content << @current_paragraph
    end
    @rich_text_parser.parse(line, @current_paragraph)    
  end
    
  # When a bullet point is seen:
  #* ensure we have a @bulleted_list_parser,
  #* create one if necessary,
  #* register its output in the @content.
  def ensure_bulleted_list_active
    if @bulleted_list_parser.nil?
      @bulleted_list_parser = BulletedListParser.new(@rich_text_parser)
      @content << @bulleted_list_parser.bulleted_list
    end
  end
    
  def ensure_verbatim_active
    @content << @verbatim = Verbatim.new { @verbatim = nil}
  end
  
  def parse_line(line)
    if @verbatim
      @verbatim.parse(line)
      return
    end
    
    @bulleted_list_parser = nil unless line.begins_with '*'

    if (line =='')
      @current_paragraph = nil
      @bulleted_list = nil
      return
    end

    if (line.begins_with('!'))
      @content << Heading.parse(line)
      return
    end

    if (line.match(/^(\*+)\s+(.*$)/))
      ensure_bulleted_list_active
      @bulleted_list_parser.parse_line(line)
      return
    end

    if (line == '{{{')
      ensure_verbatim_active
      return
    end
    
    parse_paragraph_line(line)
    return
  end	

  end
end
